/* machine_i2c.c */

#include <stdio.h>

#include "machine_i2c.h"
#include "hal_gpio.h"
#include "hal_rcc.h"
#include "hal_i2c.h"

#include "clock_init.h"


//const machine_hw_i2c_obj_t * hw_i2c_find(mp_obj_t user_obj);

void machine_hw_i2c_enable_clock(uint32_t hw_i2c_id, bool enable)
{
    switch (hw_i2c_id)
    {
        case 0u:
            RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_I2C1, enable);
            break;
        case 1u:
            RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_I2C2, enable);
            break;
        default:
            break;
    }
}

int machine_hw_i2c_transfer(mp_obj_base_t *self_in,
    uint16_t addr, /* target device address. */
    size_t n, /* num of bufs. there might be multiple buffers. */
    mp_machine_i2c_buf_t *bufs, /* buffer array. */
    unsigned int flags) /* MP_MACHINE_I2C_FLAG_READ | MP_MACHINE_I2C_FLAG_STOP */
{
    machine_hw_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in);
    uint32_t timeout;

    //i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    //i2c_master_start(cmd);
    //i2c_master_write_byte(cmd, addr << 1 | (flags & MP_MACHINE_I2C_FLAG_READ), true);

    I2C_SetTargetAddr(self->i2c_port, addr << 1 | (flags & MP_MACHINE_I2C_FLAG_READ));
    I2C_Enable(self->i2c_port, true);

    int data_len = 0;
    for (; n--; ++bufs)
    {
        if (flags & MP_MACHINE_I2C_FLAG_READ)
        {
            //i2c_master_read(cmd, bufs->buf, bufs->len, n == 0 ? I2C_MASTER_LAST_NACK : I2C_MASTER_ACK);
            I2C_PrepareToGetData(self->i2c_port);  /* Swich read-write bit, prepare to get data. */
            for (uint32_t i = 0u; i < bufs->len; i++)
            {
                timeout = self->conf->timeout;
                while ( (I2C_GetStatus(self->i2c_port) & I2C_STATUS_RX_NOTEMPTY) && (--timeout))  /* Wait to rx fifo not empty. */
                {
                }
                if (timeout == 0)
                {
                    return 0;
                }
                bufs->buf[i] = I2C_GetData(self->i2c_port);
            }
        }
        else
        {
            if (bufs->len != 0)
            {
                //i2c_master_write(cmd, bufs->buf, bufs->len, true);
                for (uint32_t i = 0u; i < bufs->len; i++)
                {
                    I2C_PutData(self->i2c_port, bufs->buf[i]);
                    timeout = self->conf->timeout;
                    while ( (I2C_GetStatus(self->i2c_port) & I2C_STATUS_TX_EMPTY) && (--timeout))  /* Wait for tx fifo empty. */
                    {
                    }
                    if (timeout == 0)
                    {
                        return 0;
                    }
                }
            }
        }
        data_len += bufs->len;
    }

    if (flags & MP_MACHINE_I2C_FLAG_STOP)
    {
        I2C_Stop(self->i2c_port);
        timeout = self->conf->timeout;
        while ( (I2C_GetStatus(self->i2c_port) & I2C_STATUS_ACTIVE) && (--timeout) )  /* Wait I2C not active, which means stop being effective. */
        {
        }
        if (timeout == 0)
        {
            return 0;
        }
    }

    // TODO proper timeout
    //esp_err_t err = i2c_master_cmd_begin(self->port, cmd, 100 * (1 + data_len) / portTICK_RATE_MS);
    //i2c_cmd_link_delete(cmd);

    return data_len;
}

/******************************************************************************/
// MicroPython bindings for hw_i2c.

STATIC void machine_hw_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind)
{
    machine_hw_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in);
    mp_printf(print, "I2C(id=%u, baudrate=%u, scl=%s, sda=%s)",
             self->i2c_id,
             self->conf->baudrate,
             qstr_str(self->scl_pin_obj->name),
             qstr_str(self->sda_pin_obj->name) );
}


mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
    MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args);

    // Parse args
    enum { ARG_id, ARG_scl, ARG_sda, ARG_freq, ARG_timeout };
    static const mp_arg_t allowed_args[] = {
        { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
        { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
        { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
        { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 100000} },
        { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 100000} }, /* 100k. */
    };
    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

    //machine_hw_i2c_obj_t * self = hw_i2c_find(args[ARG_id]);
    uint32_t i2c_id = args[ARG_id].u_int;
    if ( i2c_id >= MACHINE_HW_I2C_NUM )
    {
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't not exist. "), i2c_id);
    }
    machine_hw_i2c_obj_t * self = (machine_hw_i2c_obj_t *)machine_hw_i2c_objs[i2c_id];

    if (   (args[ARG_scl].u_obj != MP_OBJ_NULL)
        || (args[ARG_sda].u_obj != MP_OBJ_NULL)
        || (self->scl_pin_obj != args[ARG_scl].u_obj)
        || (self->sda_pin_obj != args[ARG_sda].u_obj) )
    {
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't not match with given pins. "), self->i2c_id);
    }
    if (args[ARG_freq].u_int >= 100000)
    {
        self->conf->baudrate = 100000; /* 100k. */
    }
    else
    {
        self->conf->baudrate = 50000; /* 50k. */
    }
    self->conf->timeout = args[ARG_timeout].u_int;

    /* enable i2c clock. */
    machine_hw_i2c_enable_clock(self->i2c_id, true);

    /* pin mux. */
    GPIO_Init_Type gpio_init;
    gpio_init.Speed = GPIO_Speed_50MHz;
    /* scl pin. */
    gpio_init.Pins = ( 1u << (self->scl_pin_obj->gpio_pin) );
    gpio_init.PinMode = GPIO_PinMode_AF_OpenDrain;
    GPIO_Init(self->scl_pin_obj->gpio_port, &gpio_init);
    GPIO_PinAFConf(self->scl_pin_obj->gpio_port, gpio_init.Pins, self->scl_pin_af);
    /* sda pin. */
    gpio_init.Pins = ( 1u << (self->sda_pin_obj->gpio_pin) );
    gpio_init.PinMode = GPIO_PinMode_AF_OpenDrain;
    GPIO_Init(self->sda_pin_obj->gpio_port, &gpio_init);
    GPIO_PinAFConf(self->sda_pin_obj->gpio_port, gpio_init.Pins, self->sda_pin_af);

    /* conf i2c master. */
    /* Setup I2C initialization values. */
    I2C_Master_Init_Type i2c_init;
    i2c_init.ClockFreqHz = CLOCK_APB1_FREQ;
    if (self->conf->baudrate >= 100000)
    {
        i2c_init.BaudRate = I2C_BaudRate_100K;
    }
    else
    {
        i2c_init.BaudRate = I2C_BaudRate_50K;
    }
    I2C_InitMaster(self->i2c_port, &i2c_init);

    return MP_OBJ_FROM_PTR(self);
}

STATIC const mp_machine_i2c_p_t machine_hw_i2c_p =
{
    .transfer = machine_hw_i2c_transfer,
};

const mp_obj_type_t machine_hw_i2c_type =
{
    { &mp_type_type },
    .name = MP_QSTR_HW_I2C,
    .print = machine_hw_i2c_print,
    .make_new = machine_hw_i2c_make_new,
    .protocol = &machine_hw_i2c_p,
    .locals_dict = (mp_obj_dict_t *)&mp_machine_i2c_locals_dict,
};

#if 0
const machine_hw_i2c_obj_t *hw_i2c_find(mp_obj_t user_obj)
{
    if ( mp_obj_is_type(user_obj, &machine_hw_i2c_type) )
    {
        return user_obj;
    }

    if ( mp_obj_is_small_int(user_obj) )
    {
        uint8_t idx = MP_OBJ_SMALL_INT_VALUE(user_obj);
        if ( idx < MACHINE_HW_I2C_NUM )
        {
            return machine_hw_i2c_objs[idx];
        }
    }

    mp_raise_ValueError(MP_ERROR_TEXT("HW I2C doesn't exist"));
}
#endif

/* EOF. */

